/**
 * node 中的模块: 内置模块, 核心模块, 第三方模块
 *
 * node中的模块是有规范的 commonjs 规范, es6Module
 *
 * esModule 模块叫 "静态导入"  (只能在文件最开始引入 import )
 *        静态导入的好处: 在编译的时候, 就可以知道使用了哪些变量, 可以实现 tree-shaking
 *
 * commonjs 模块是 "动态导入". commonjs 模块不支持 tree-shaking
 */

// commonjs 模块定义了自己的规范, 按照规范来使用就可以.
// 1. 如果想使用哪个模块, 就直接 require 即可, 后缀可以省略,默认会查找.js文件, 没有js 找 .json
// 2. 如果这个模块需要被别人使用,需要导出当前模块
// 3.在node中, 每个 js/json 文件都是一个模块
// 4. 一个包中,含有多个文件. (一般情况下, 每个包都配有一个 package.json 文件)

// let a = require('./a')
// console.log(a);

// node中, require 把文件读取过来的时候, 外面会包装一个函数. 而且是一个自执行函数
// require("./a"} 读取文件后, 会在读取的文件外层添加一个自执行函数.
// 并且注入 五个 参数. 最终在代码中 return 出去
// const r = require("./a")
// const r = function (module, exports, require, __dirname, __filename) {
//   return module.exports = "hello";
// }();
// console.log(r);


// require 的原理就是
// 1. 读取文件  2. 包装函数, 设置参数,  3, 默认返回 module.exports 对象.


// node 1.module.js --inspect-brk


// node中的 模块的实现原理.

// 1. 实现一个 require 方法
// 2. Module._load 实现模块的加载
//3. Module._resolveFilename 把文件名解析成要给 绝对路径出来
//4. 实现模块的缓存.(根据绝对路径, 进行模块的缓存).
//5. module.exports 会在第一次缓存起来, 后续再去使用的话, 会取上一次的结果.
// 6. 创建一个模块, 根据文件路径来创建. 一个模块必须要有三个属性 id, path, exports
// 7. 根据创建的模块进行模块的加载.
// 8. 加载模块时, 会构建一个 paths 属性. 这个属性就是第三方模块的查找路径
//9. 取出文件的后缀, Module._extensions 调用对应的加载模块的策略.
// 10. 读取文件内容
//11. 在文件内容外面包含一个函数
// 12. 最终require 方法会拿到 module.exports的返回结果.

  // 最终用户使用的结果都来自于 module.exports 如果只是改变 exports引用, 不会影响 module.exports的值,
  // 但是给exports 值增添属性, 是可以修改 module.exports的. 注意不要同时使用 exports 和 module.exports
  // 否则会以module.exports 结果为基准.

  // 直接操作 global属性, 就可以不用导出. 但是会污染全局变量, 不建议使用.

const path = require("path")
const fs = require("fs")
const vm = require("vm")

function Module(id) {
  this.id = id;
  this.exports = {} // 模块对应的导出结果


}

Module._extensions = {
  ".js"(module) {
    let script = fs.readFileSync(module.id, "utf8")
    let template = `(function(exports,module,require,__filename,__dirname){${script}})`
    // vm.runInThisContext 类似 eval 和 new Function, 在node中,它可以为我们创建一个全新的函数环境
    let compileFunction = vm.runInThisContext(template)

    let exports = module.exports; // module.exports 就是一个exports,为了实现一个简写
    let thisValue = exports; // this = exports = module.exports = {}
    let filename = module.id
    let dirname = path.dirname((filename))
    compileFunction.call(thisValue, exports, module, myRequire, filename, dirname)
  },
  ".json"(module) {
    let script = fs.readFileSync(module.id, "utf8")
    module.exports = JSON.parse(script) // 直接将json挂载到exports对象上, 这样用户可以直接require json文件,拿到的就是json文件.
  },
}

Module._resolveFilename = function (filename) {
  const filePath = path.resolve(__dirname, filename)
  let exists = fs.existsSync(filePath)
  if (exists) return filePath;
  // 尝试添加 .js 或者 .json 后缀
  let keys = Reflect.ownKeys(Module._extensions)
  for (let i = 0; i < keys.length; i++) {
    let newPath = filePath + keys[i]
    if (fs.existsSync(newPath)) return newPath
  }
  throw  new Error("module not found")
}

Module._cache = {}; // 用来做缓存

Module.prototype.load = function () {
  let extension = path.extname(this.id); // 要加载的文件名
  Module._extensions[extension] && Module._extensions[extension](this)

}

function myRequire(filename) {// 文件路径可能不具备后缀
  let filePath = Module._resolveFilename(filename)

  let exists = Module._cache[filePath]
  if (exists) {
    return exists.exports;
  }
  //2. 创造一个模块
  let module = new Module(filePath)
  Module._cache[filePath] = module

  // 获取模块中的内容, 包装函数, 让函数执行, 用户的路基会给module.exports 赋值
  module.load()
  return module.exports // 最后返回的结果
}

const r = myRequire("./a")
const r1 = myRequire("./b")
console.log("myRequire导入js====>", r);
console.log("myRequire导入json====>", r1);
